<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../../web-component-tester/browser.js"></script>
  <link rel="import" href="../../polymer.html">
</head>
<body>
<template></template>
<script>

// TODO(sorvell): cannot register element in main document under polyfill.
var registered = false;
function registerTestElement() {
  if (registered) return;
  var template = document.querySelector('template');
  Polymer({
    is: 'x-content-test',
    _useContent: true,
    _template: template,
    _encapsulateStyle: false
  });
  registered = true;
}

/**
 * Test the `<content>` element distribution algorithm by verifying the
 * resulting composed tree structure.
 */
function testRender(descr, hostInnerHtml, shadowRootHtml, expectedHtml) {
  test(descr, function() {
    registerTestElement();
    // Create an instance of the test element.
    var host = document.createElement('x-content-test');
    // Populate the initial pool of light DOM children.
    //host.innerHTML = hostInnerHtml;
    setInnerHTML(host, hostInnerHtml);
    syncLightDOM(host);
    // Pretend we're stamping the template contents.
    setRootInnerHTML(host.shadyRoot, shadowRootHtml);
    // Invoke distribution and verify the resulting tree.
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), expectedHtml);
  });
}

testRender('Empty shadow', 'abc', '', '');
testRender('Simple shadow', 'abc', 'def', 'def');
testRender('Fallback shadow', 'abc',
           '<content select="xxx">fallback</content>', 'fallback');
testRender('Content', 'abc',
           '<content>fallback</content>', 'abc');
testRender('Content before', 'abc',
           'before<content>fallback</content>', 'beforeabc');
testRender('Content after', 'abc',
           '<content>fallback</content>after', 'abcafter');

suite('render content', function() {
  testRender('no select', '<a href="">Link</a> <b>bold</b>',
             '<content></content>',
             '<a href="">Link</a> <b>bold</b>');
  testRender('select ""', '<a href="">Link</a> <b>bold</b>',
             '<content select=""></content>',
             '<a href="">Link</a> <b>bold</b>');
  testRender('select *', '<a href="">Link</a> <b>bold</b>',
             '<content select="*"></content>',
             '<a href="">Link</a><b>bold</b>');

  testRender('select .a',
             '<a class="a">a</a> <a class="b">b</a>',
             '<content select=".a"></content>',
             '<a class="a">a</a>');

  testRender('select .b .a',
             '<a class="a">a</a> <a class="b">b</a>',
             '<content select=".b"></content><content select=".a"></content>',
             '<a class="b">b</a><a class="a">a</a>');

  testRender('select .b .a 2',
             '<a class="a">a</a> <a class="b">b</a> <a class="b">c</a>',
             '<content select=".b"></content><content select=".a"></content>',
             '<a class="b">b</a><a class="b">c</a><a class="a">a</a>');

  testRender('select [c] *',
             '<span>a</span><span>b</span><span c>c</span><span>d</span>',
             '<content select="[c]"></content><content></content>',
             '<span c="">c</span><span>a</span><span>b</span><span>d</span>');
});


test('Reproject', function() {
  registerTestElement();
  var host = document.createElement('x-content-test');
  setInnerHTML(host, '<a></a>');
  // pre-distirbution grab first composed node.
  var a = getComposedChildAtIndex(host, 0);
  syncLightDOM(host);
  setRootInnerHTML(host.shadyRoot, '<x-content-test id="p"></x-content-test>');
  // force upgrade on polyfilled browsers
  var p = host.shadyRoot.firstChild;
  CustomElements.upgrade(p);
  setInnerHTML(p, '<b></b><content></content>');
  syncLightDOM(p);
  //p.innerHTML = '<b></b><content></content>';
  updateRootInsertionPoints(host.shadyRoot);
  var b = p.firstChild;
  var content = p.lastChild;



  syncLightDOM(p);
  setRootInnerHTML(p.shadyRoot,
      'a: <content select=a></content>b: <content select=b></content>');
  var textNodeA = p.shadyRoot.firstChild;
  var contentA = p.shadyRoot.childNodes[1];
  var textNodeB = p.shadyRoot.childNodes[2];
  var contentB = p.shadyRoot.childNodes[3];

  function testRender() {
    // Simulate the correct ordering as "ready" would fire.
    distributeContentNow(host);
    // NOTE: needed only for this imperative test that needs
    // to simulate distribution from `shadyRoot`
    distributeContentNow(p);
    assert.strictEqual(getComposedHTML(host),
        '<x-content-test id="p">a: <a></a>b: <b></b></x-content-test>');

    assertArrayEqual(host.__dom.childNodes, [a]);
    assert.strictEqual(a.__dom.parentNode, host);
    assertArrayEqual(host.shadyRoot.__dom.childNodes, [p]);
    assert.strictEqual(p.__dom.parentNode, host.shadyRoot);
    assertArrayEqual(p.__dom.childNodes, [b, content]);
    assert.strictEqual(b.__dom.parentNode, p);
    assert.strictEqual(content.__dom.parentNode, p);
    assertArrayEqual(p.shadyRoot.__dom.childNodes,
        [textNodeA, contentA, textNodeB, contentB]);
  }

  testRender();
  testRender();
});


suite('Mutate light DOM', function() {
  test('removeAllChildNodes - mutate host', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content>fallback</content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    host.firstChild.textContent = '';
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a></a>');

    host.__dom.childNodes = [];
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), 'fallback');
  });


  test('removeAllChildNodes - mutate shadow', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content><b>after</b>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b>after</b>');

    host.shadyRoot.__dom.childNodes[1].textContent = '';
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');

    host.shadyRoot.__dom.childNodes = [];
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '');
  });

  test('removeAllChildNodes - mutate shadow fallback', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content select="xxx"><b>fallback</b></content>');
    var b = host.shadyRoot.firstChild.firstChild;
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<b>fallback</b>');

    b.textContent = '';
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<b></b>');

    host.shadyRoot.firstChild.__dom.childNodes = [];
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '');

    host.shadyRoot.__dom.childNodes = [];
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '');
  });

  test('removeChild - mutate host', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content>fallback</content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    host.firstChild.removeChild(host.firstChild.firstChild);
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a></a>');

    host.__dom.childNodes = [];
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), 'fallback');
  });

  test('removeChild - mutate host 2', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a></a><b></b>');
    var a = getComposedChildAtIndex(host, 0);
    var b = getComposedChildAtIndex(host, 1);

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content>fallback</content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a></a><b></b>');

    Polymer.dom(host).removeChild(b);
    Polymer.dom.flush();
    assert.strictEqual(getComposedHTML(host), '<a></a>');

    Polymer.dom(host).removeChild(a);
    Polymer.dom.flush();
    assert.strictEqual(getComposedHTML(host), 'fallback');
  });

  test('removeChild - mutate shadow', function() {
    var host = document.createElement('x-content-test');

    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content><b>after</b>');
    var b = host.shadyRoot.lastChild;
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b>after</b>');

    b.removeChild(b.firstChild);
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');

    host.shadyRoot.__dom.childNodes.splice(1, 1); // remove b
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    host.shadyRoot.__dom.childNodes = []; // remove a
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '');
  });

  test('setAttribute select', function() {
    // TODO(arv): DOM bindings for select.
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a><b>World</b>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content select="b">fallback b</content>' +
                            '<content select="a">fallback a</content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<b>World</b><a>Hello</a>');

    host.shadyRoot.firstChild.setAttribute('select', 'xxx');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), 'fallback b<a>Hello</a>');

    host.shadyRoot.firstChild.setAttribute('select', '');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b>World</b>fallback a');
  });

  test('appendChild - mutate host', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    Polymer.dom(host).appendChild(b);
    Polymer.dom.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');
  });

  test('appendChild - mutate shadow', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    Polymer.dom(host.shadyRoot).appendChild(b);
    Polymer.dom.flush();
    //distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');
  });

  test('insertBefore - mutate host', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');
    var a = getComposedChildAtIndex(host, 0);

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    Polymer.dom(host).insertBefore(b, a);
    Polymer.dom.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b><a>Hello</a>');
  });

  test('insertBefore - mutate shadow', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content>');
    var content = host.shadyRoot.firstChild;
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    Polymer.dom(host.shadyRoot).insertBefore(b, content);
    Polymer.dom.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b><a>Hello</a>');
  });

  test('replaceChild - mutate host', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content>');
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    host.__dom.childNodes[0] = b;
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<b></b>');
  });

  test('replaceChild - mutate shadow', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<a>Hello</a>');

    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content>');
    var content = host.shadyRoot.firstChild;
    distributeContentNow(host);
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    Polymer.dom(host.shadyRoot).replaceChild(b, content);
    Polymer.dom.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b>');
  });

  test('querySelectorAll (shadyRoot)', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<div id="main"></div>');
    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content><span id="main"></span>' +
      '<x-content-test></x-content-test>');
    var hostLocalMain = getComposedChildAtIndex(host.shadyRoot, 1);
    var child = getComposedChildAtIndex(host.shadyRoot, 100);
    // force upgrade on polyfilled browsers
    CustomElements.upgrade(child);
    setInnerHTML(child, '<div id="sub"></div>');
    var childLightSub = getComposedChildAtIndex(child, 0);
    syncLightDOM(child);
    setRootInnerHTML(child.shadyRoot, '<content></content><span id="sub"></span>');
    var childLocalSub = child.shadyRoot.lastChild;
    distributeContentNow(host);
    // NOTE: needed only for this imperative test that needs
    // to simulate distribution from `shadyRoot`
    child._distributeContent();

    assert.deepEqual(Polymer.dom(host.root).querySelectorAll('span#main'), [hostLocalMain]);
    assert.deepEqual(Polymer.dom(host.root).querySelectorAll('div#sub'), [childLightSub]);
    assert.deepEqual(Polymer.dom(child.root).querySelectorAll('span#sub'), [childLocalSub]);
  });

  test('querySelectorAll (light dom)', function() {
    var host = document.createElement('x-content-test');
    setInnerHTML(host, '<div id="main"></div>');
    var hostLightMain = getComposedChildAtIndex(host, 0);
    syncLightDOM(host);
    setRootInnerHTML(host.shadyRoot, '<content></content><span id="main"></span>' +
      '<x-content-test></x-content-test>');
    var child = getComposedChildAtIndex(host.shadyRoot, 100);
    // force upgrade on polyfilled browsers
    CustomElements.upgrade(child);
    setInnerHTML(child, '<div id="sub"></div>');
    var childLightSub = getComposedChildAtIndex(child, 100) ;
    syncLightDOM(child);
    setRootInnerHTML(child.shadyRoot, '<content></content><span id="sub"></span>');
    distributeContentNow(host);
    assert.deepEqual(Polymer.dom(host).querySelectorAll('div#main'), [hostLightMain]);
    assert.deepEqual(Polymer.dom(host).querySelectorAll('#sub'), []);
    assert.deepEqual(Polymer.dom(child).querySelectorAll('div#sub'), [childLightSub]);
  });

});

function syncLightDOM(n) {
  if (n.__dom.childNodes !== undefined) {
    var c$ = Array.prototype.slice.call(n.childNodes);
    n.__dom.firstChild = c$[0];
    n.__dom.lastChild = c$[c$.length-1];
    c$.forEach(function(c, i) {
      if (n.__dom.childNodes.indexOf(c) < 0) {
        c.__dom = c.__dom || {};
        c.__dom.parentNode = n;
        c.__dom.previousSibling = c$[i-1];
        c.__dom.nextSibling = c$[i+1];
        n.__dom.childNodes.push(c);
      }
    });
  }
}

var nativeAppendChild = Element.prototype.appendChild;

function setInnerHTML(node, value) {
  node.textContent = '';
  var temp = node.ownerDocument.createElement('div');
  temp.innerHTML = value;
  var firstChild;
  while ((firstChild = temp.firstChild)) {
    nativeAppendChild.call(node, firstChild);
  }
}

function setRootInnerHTML(root, value) {
  setInnerHTML(root, value);
  syncLightDOM(root);
  updateRootInsertionPoints(root);
}

function updateRootInsertionPoints(root) {
  Polymer.dom(root.host)._updateInsertionPoints(root.host);
}

function getComposedHTML(node) {
  return node.innerHTML;
}

function getComposedChildAtIndex(node, index) {
  var c$ = node.childNodes;
  if (c$) {
    index = index || 0;
    index = Math.max(0, Math.min(index, c$.length-1));
    return c$[index];
  }
}

function assertArrayEqual(a, b, msg) {
  assert.equal(a.length, b.length, msg);
  for (var i = 0; i < a.length; i++) {
    assert.equal(a[i], b[i], msg);
  }
}

function distributeContentNow(node) {
  node.distributeContent();
  Polymer.dom.flush();
}

</script>
</body>
</html>
